﻿/* Copyright (C) 2011 by John Cronin
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

using System;
using System.Collections.Generic;
using System.Text;

namespace tysos.x86_64
{
    class Arch : tysos.Arch
    {
        ulong bda_va;
        ulong pmem_bitmap_va;
        ulong vmem_temppage_va;
        ulong vga_fb_va;

        ulong cpu_va;

        ulong cpu_datasize;

        internal override tysos.TaskSwitchInfo CreateTaskSwitchInfo()
        {
            x86_64.TaskSwitchInfo ret = new TaskSwitchInfo();
            ret.send_eoi = Program.stab.GetAddress("__lapic_eoi");
            return ret;
        }

        internal static ulong GetRecommendedChunkLength()
        {
            /* We need 1 page for the bda, one for the pmem bitmap, one for the 
             * virtual memory temporary page and one for the vga frame buffer,
             * and then some heap space */
            return 0x4000;
        }

        internal override ulong PageSize
        {
            get { return 0x1000; }
        }

        internal override int PointerSize
        {
            get { return 8; }
        }

        internal override void Init(UIntPtr chunk_vaddr, UIntPtr chunk_length, Multiboot.Header mboot)
        {
            /* Set up an x86_64 environment */

            // Enable sse (used for virtftnptr stores and loads) - see Intel 3a:13.1.4
            ulong cr4 = ABI.OtherOperations.ReadCR4();
            cr4 |= 0x200;       // set cr4.OSFXSR
            ABI.OtherOperations.WriteCR4(cr4);
            cr4 |= 0x400;       // set cr4.OSXMMXCPT
            ABI.OtherOperations.WriteCR4(cr4);

            ulong cr0 = ABI.OtherOperations.ReadCR0();
            cr0 |= 0x4;
            cr0 ^= 0x4;         // clear cr0.EM
            ABI.OtherOperations.WriteCR0(cr0);
            cr0 |= 0x2;         // set cr0.MP
            ABI.OtherOperations.WriteCR0(cr0);

            /* Set-up the MXCSR register
             * 
             * We want to unmask all exceptions except the precision exception which is thrown if the
             * result of an operation cannot be stored with complete precision in an xmm register,
             * e.g. 1/3 - this happens frequently and is expected, so we don't not want to raise a #XM
             * exception here.  The precision mask is bit 12.
             * 
             * We set the rounding mode (bits 13 and 14) to 00b which is round to even as the Math.Round
             * function expects this mode
             * 
             * All other bits are cleared.
             */

            uint mxcsr = 0x1000;
            ABI.OtherOperations.WriteMxcsr(mxcsr);

            /* Set up the memory managers */
            bda_va = (ulong)chunk_vaddr;
            pmem_bitmap_va = bda_va + 0x1000;
            vmem_temppage_va = bda_va + 0x2000;
            vga_fb_va = bda_va + 0x3000;

            VirtMem = new VirtMem(null, vmem_temppage_va);
            PhysMem = new Pmem(pmem_bitmap_va, 0x1000);

            /* Map the bda */
            VirtMem.map_page(bda_va, 0x0);

            /* Set up the debug outputs */
            BootInfoOutput = new Vga(bda_va, vga_fb_va, VirtMem);
            DebugOutput = new SerialDebug();

            // Set up the physical memory allocator
            // First mark all pages as free, then unmark those which are used
            for (int i = 0; i < mboot.mmap.Length; i++)
            {
                Multiboot.MemoryMap cur_mmap = mboot.mmap[i];
                if (cur_mmap.type == Multiboot.MemoryMapType.Available)
                {
                    for (ulong cur_page = cur_mmap.base_addr; (cur_page + 0x1000) <= (cur_mmap.base_addr + cur_mmap.length); cur_page += 0x1000)
                        PhysMem.ReleasePage(cur_page);
                }
            }
            for (int i = 0; i < mboot.mmap.Length; i++)
            {
                Multiboot.MemoryMap cur_mmap = mboot.mmap[i];
                if (cur_mmap.type != Multiboot.MemoryMapType.Available)
                {
                    for (ulong cur_page = cur_mmap.base_addr; cur_page < (cur_mmap.base_addr + cur_mmap.length); cur_page += 0x1000)
                        PhysMem.MarkUsed(cur_page);
                }
            }

            // Display success
            Formatter.Write("Allocated: ", DebugOutput);
            Formatter.Write(PhysMem.GetFreeCount(), DebugOutput);
            Formatter.WriteLine(" 4 kiB pages", DebugOutput);
            VirtMem.SetPmem(PhysMem);

            /* Set up the virtual region allocator */
            VirtualRegions = new Virtual_Regions(mboot.max_tysos);

            /* Set up interrupts */
            ulong idt_start = VirtualRegions.Alloc(256 * 16, 0x1000, "idt");
            VirtMem.map_page(idt_start);
            Interrupts = new Interrupts(idt_start);

            // Install the page fault handler
            Interrupts.InstallHandler(14, new Interrupts.ISREC(PageFault.PFHandler));

            // Install some default handlers
            Interrupts.InstallHandler(0, new Interrupts.ISR(Exceptions.DivideError_0_Handler));
            Interrupts.InstallHandler(6, new Interrupts.ISR(Exceptions.InvalidOpcode_6_Handler));
            Interrupts.InstallHandler(8, new Interrupts.ISREC(Exceptions.DoubleFault_8_Handler));
            Interrupts.InstallHandler(13, new Interrupts.ISREC(Exceptions.GeneralProtection_13_Handler));
            Interrupts.InstallHandler(17, new Interrupts.ISREC(Exceptions.AlignmentCheck_17_Handler));
            Interrupts.InstallHandler(19, new Interrupts.ISR(Exceptions.SIMD_19_Handler));

            /* Set up the tss and kernel stack */
            ulong tss_start;
            unsafe
            {
                tss_start = VirtualRegions.Alloc((ulong)sizeof(tysos.x86_64.Tss.Tss_struct), 0x1000, "tss");
            }
            ulong rsp0_start = VirtualRegions.Alloc(VirtMem.page_size, VirtMem.page_size, "rsp0");
            ulong rsp0 = rsp0_start + VirtMem.page_size;

            VirtMem.map_page(rsp0_start);
            VirtMem.map_page(tss_start);
            tysos.x86_64.Tss tss = new tysos.x86_64.Tss(mboot.gdt, tss_start, rsp0);

            // Now point the heap to its proper location
            MemoryAllocator.DefineHeap(0xFFFF800000000000, 0xFFFFFF8000000000);

            /* Load ACPI tables */
            tysos.x86_64.Acpi acpi = new tysos.x86_64.Acpi(VirtualRegions, VirtMem, bda_va);

            /* Disable the PIC if we have one */
            if ((acpi.Apic != null) && (acpi.Apic.Has8259))
                tysos.x86_64.PIC_8295a.Disable();


            /* Set up the local apic for the current processor */
            tysos.x86_64.LApic bsp_lapic = new tysos.x86_64.LApic(VirtualRegions, VirtMem);

            /* Calibrate the lapic */
            if (acpi.Hpet == null)
                bsp_lapic.CalibrateDirectly(133000000.0);
            else
            {
                tysos.x86_64.Hpet hpet = new tysos.x86_64.Hpet(VirtualRegions, VirtMem, acpi.Hpet.paddr);
                bsp_lapic.CalibrateTimerWithHpet(hpet, 1000000);
            }

            bsp_lapic.SetSpuriousVector(0x60);
            Interrupts.InstallHandler(0x60, new Interrupts.ISR(tysos.x86_64.LApic.SpuriousApicInterrupt));

            bsp_lapic.SetTimer(true, 100.0, 0x40);      // 10 ms timer
            //bsp_lapic.SetTimer(true, 0x144b50, 0x40);   // 10ms timer with 133 Mhz bus and divisor 1, interrupt vector 0x40
            Interrupts.InstallHandler(0x40, new Interrupts.ISR(tysos.x86_64.LApic.TimerInterrupt));
            SchedulerTimer = bsp_lapic;

            /* Set up the current cpu */
            cpu_datasize = (ulong)new x86_64_cpu().RequiredDataSize;
            Virtual_Regions.Region cpu_reg = VirtualRegions.AllocRegion(cpu_datasize, 0x1000, "BSP cpu", 0, Virtual_Regions.Region.RegionType.CPU_specific);
            Program.cur_cpu_data = new x86_64_cpu(cpu_reg, 0);
            ((x86_64_cpu)Program.cur_cpu_data).CurrentLApic = bsp_lapic;

            Processors = new List<Cpu>();
            Processors.Add(Program.cur_cpu_data);

             /* Set up the task switcher */
            Switcher = new tysos.x86_64.TaskSwitcher();
       }

        internal override void EnableMultitasking()
        {
            ((x86_64.x86_64_cpu)Program.cur_cpu_data).CurrentLApic.Enable();
            ABI.OtherOperations.Sti();
        }
    }
}
